package com.company.cloud.acct.modules.app.service.impl;

import cn.hutool.core.util.EnumUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.company.cloud.acct.constant.AcctErrorEnum;
import com.company.cloud.acct.modules.api.constant.*;
import com.company.cloud.acct.modules.api.request.AcctGetRequest;
import com.company.cloud.acct.modules.api.request.AcctOpRequest;
import com.company.cloud.acct.modules.api.response.AccountModel;
import com.company.cloud.acct.modules.app.service.AcctOpService;
import com.company.cloud.acct.modules.base.entity.AccountBillInfo;
import com.company.cloud.acct.modules.base.entity.AccountDetailInfo;
import com.company.cloud.acct.modules.base.entity.AccountInfo;
import com.company.cloud.acct.modules.base.entity.AccountOpLogInfo;
import com.company.cloud.acct.modules.base.service.AccountBillInfoService;
import com.company.cloud.acct.modules.base.service.AccountDetailInfoService;
import com.company.cloud.acct.modules.base.service.AccountInfoService;
import com.company.cloud.acct.modules.base.service.AccountOpLogInfoService;
import com.company.cloud.acct.modules.cache.utils.AcctConvertUtils;
import com.company.cloud.common.asserts.ValidatorUtils;
import com.company.cloud.common.bean.LoginSimpleUserInfo;
import com.company.cloud.common.constant.BaseCodeEnum;
import com.company.cloud.common.constant.BaseConstant;
import com.company.cloud.common.constant.BaseReturnEnum;
import com.company.cloud.common.exception.BusinessException;
import com.company.cloud.common.utils.EnumUtils;
import com.company.cloud.common.utils.LocalDateTimeUtils;
import com.company.cloud.common.utils.StrSplitUtils;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;


import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

@Log4j2
@Service
public class AcctOpServiceImpl implements AcctOpService {

    @Autowired
    private AccountInfoService accountInfoService;
    @Autowired
    private AccountDetailInfoService accountDetailInfoService;
    @Autowired
    private AccountBillInfoService accountBillInfoService;
    @Autowired
    private AccountOpLogInfoService accountOpLogInfoService;


    @Override
    public AccountModel getAcctInfo(Integer userId, String type) {
        AccountInfo entity = accountInfoService.getOne(new QueryWrapper<AccountInfo>()
                .eq("user_id", userId)
                .eq("type", type)
                .eq("state", AcctStateEnum.NORMAL.getCode())
        );

        return AcctConvertUtils.getAccountModel(entity);
    }


    //todo:移到基础服务中
    private <E extends BaseCodeEnum> E getEnum(final Class<E> enumClass, String code, String errorMsg) {
        E data = EnumUtils.getBaseCodeEnum(enumClass, code);
        if (data == null) {
            throw new BusinessException(BaseReturnEnum.PARAM_ERROR, errorMsg);
        }
        return data;
    }

    /**
     * 账户明细生成(生效)
     */
    private AccountDetailInfo getAccountDetailValidity(BigDecimal fund, String relObjType, String relObjNo, String overdueFundDate, AccountInfo acct, LoginSimpleUserInfo userInfo) {
        AccountDetailInfo entity = new AccountDetailInfo();
        entity.setCompanyNo(acct.getCompanyNo());
        entity.setUserId(acct.getUserId());
        entity.setType(acct.getType());
        entity.setValidityFund(fund);
        entity.setValidityDate(LocalDateTime.now());
        entity.setValidityState(BaseConstant.YNEnum.YES.getCode());
        entity.setValidityRelObjType(relObjType);
        entity.setValidityRelObjNo(relObjNo);
        entity.setConsumeFund(BigDecimal.ZERO);
//        entity.setConsumeDate();
        entity.setOverdueFund(BigDecimal.ZERO);
        entity.setOverdueState(BaseConstant.YNEnum.NO.getCode());
        if (!StringUtils.isEmpty(overdueFundDate)) {
            entity.setOverdueDate(LocalDateTimeUtils.parse(overdueFundDate, LocalDateTimeUtils._yyyyMMddHHmmss));
        }
        entity.addCreateParam(userInfo);
        entity.addEditParam(userInfo);
        return entity;
    }

    /**
     * 账户明细消费
     */
    private List<AccountDetailInfo> getAccountDetailConsume(BigDecimal fund, String relObjType, String relObjNo, AccountInfo acct, LoginSimpleUserInfo userInfo) {
        List<AccountDetailInfo> acctDetailList = accountDetailInfoService.list(
                new QueryWrapper<AccountDetailInfo>()
                        .eq("user_id", acct.getUserId())
                        .eq("type", acct.getType())
                        .apply(" (validity_fund-consume_fund-overdue_fund)>0")
                        .orderByAsc("cdt")
                        .last(" LIMIT 10000")
        );
        if (CollectionUtils.isEmpty(acctDetailList)) {
            throw new BusinessException(AcctErrorEnum.Acct_Detail_Insufficient);
        }
        BigDecimal FUND = fund;
        List<AccountDetailInfo> updateDetailList = new ArrayList<AccountDetailInfo>();
        for (AccountDetailInfo detail : acctDetailList) {
            if (FUND.doubleValue() <= 0) {
                break;
            }
            BigDecimal acctOpFund = detail.getValidityFund().subtract(detail.getConsumeFund()).subtract(detail.getOverdueFund());

            if (FUND.doubleValue() >= acctOpFund.doubleValue()) {
                detail.setConsumeFund(detail.getConsumeFund().add(acctOpFund));
                FUND = FUND.subtract(acctOpFund);
            } else {
                detail.setConsumeFund(detail.getConsumeFund().add(FUND));
                FUND = BigDecimal.ZERO;
            }
            detail.setConsumeRelObjType(relObjType);
            detail.setConsumeRelObjNo(relObjNo);
            detail.addEditParam(userInfo);
            updateDetailList.add(detail);
        }

        if (FUND.doubleValue() > 0) {
            throw new BusinessException(AcctErrorEnum.Acct_Detail_Insufficient);
        }
        return updateDetailList;
    }


    /**
     * 账单已结算
     */
    private AccountBillInfo getAccountBillSettle(AcctOpRequest param, String companyNo, LoginSimpleUserInfo userInfo) {
        AccountBillInfo entity = new AccountBillInfo();
        entity.setCompanyNo(companyNo);
        entity.setUserId(param.getUserId());
        entity.setType(param.getType());
        entity.setRelBizName(param.getRelBizName());
        entity.setRelObjType(param.getRelObjType());
        entity.setRelObjNo(param.getRelObjNo());
        entity.setRelDate(param.getRelDate());
//        entity.setInOut(AcctInOutEnum.In_Acct.getCode());
        entity.setBusiType(param.getBusiType());
        entity.setFundOp(param.getFundOp());
        entity.setFundTax(param.getFundTax());
        entity.setFundReal(param.getFundReal());
        entity.setMemo(param.getMemo());
        entity.setTags(StrSplitUtils.list2string(param.getTags()));
        entity.setBillDate(LocalDateTime.now());
        entity.setState(AcctBillStateEnum.Already_Settle.getCode());
        if (!CollectionUtils.isEmpty(param.getExtendMap())) {
            entity.setBillExtendInfo(JSON.toJSONString(param.getExtendMap()));
        }
        entity.addCreateParam(userInfo);
        entity.addEditParam(userInfo);
        return entity;
    }

    /**
     * 账单结算中扣除
     */
    private List<AccountBillInfo> getAccountBillSettleIng(AcctOpRequest param, AcctBillStateEnum state, LoginSimpleUserInfo userInfo) {
        List<AccountBillInfo> billlList = accountBillInfoService.list(
                new QueryWrapper<AccountBillInfo>()
                        .eq("user_id", param.getUserId())
                        .eq("type", param.getType())
                        .eq("rel_obj_type", param.getRelObjType())
                        .eq("rel_obj_no", param.getRelObjNo())
                        .eq("state", state.getCode())
        );

        if (CollectionUtils.isEmpty(billlList)) {
            throw new BusinessException(AcctErrorEnum.Acct_Bill_Insufficient);
        }

        BigDecimal FundOp = param.getFundOp();
        BigDecimal FundTax = param.getFundTax();
        BigDecimal FundReal = param.getFundReal();
        List<AccountBillInfo> updateBillList = new ArrayList<AccountBillInfo>();
        for (AccountBillInfo bill : billlList) {
            if (FundOp.doubleValue() <= 0 || FundTax.doubleValue() <= 0 || FundReal.doubleValue() <= 0) {
                break;
            }
            if (FundOp.doubleValue() >= bill.getFundOp().doubleValue()) {
                bill.setFundOp(BigDecimal.ZERO);
                FundOp = FundOp.subtract(bill.getFundOp());
            } else {
                bill.setFundOp(bill.getFundOp().subtract(FundOp));
                FundOp = BigDecimal.ZERO;
            }

            if (FundTax.doubleValue() >= bill.getFundTax().doubleValue()) {
                bill.setFundTax(BigDecimal.ZERO);
                FundTax = FundTax.subtract(bill.getFundTax());
            } else {
                bill.setFundTax(bill.getFundTax().subtract(FundTax));
                FundTax = BigDecimal.ZERO;
            }

            if (FundReal.doubleValue() >= bill.getFundReal().doubleValue()) {
                bill.setFundReal(BigDecimal.ZERO);
                FundReal = FundReal.subtract(bill.getFundReal());
            } else {
                bill.setFundReal(bill.getFundReal().subtract(FundReal));
                FundReal = BigDecimal.ZERO;
            }
            if (bill.getFundReal().doubleValue() <= 0) {
                bill.setState(AcctBillStateEnum.Already_Settle.getCode());
            }
            bill.addEditParam(userInfo);
            updateBillList.add(bill);
        }
        if (FundOp.doubleValue() > 0 || FundTax.doubleValue() > 0 || FundReal.doubleValue() > 0) {
            throw new BusinessException(AcctErrorEnum.Acct_Bill_Insufficient);
        }
        return updateBillList;
    }


    private AccountOpLogInfo getAccountOpLog(AcctOpRequest param, AccountInfo acct, AcctOpTypeEnum opType, LoginSimpleUserInfo userInfo) {
        AccountOpLogInfo entity = new AccountOpLogInfo();
        entity.setCompanyNo(acct.getCompanyNo());
        entity.setUserId(acct.getUserId());
        entity.setType(acct.getType());
        entity.setOpType(opType.getCode());

        entity.setFundOp(param.getFundOp());
        entity.setFundTax(param.getFundTax());
        entity.setFundReal(param.getFundReal());

        entity.setPreTotalFund(acct.getTotalFund());
        entity.setPostTotalFund(acct.getTotalFund());
        entity.setPreFund(acct.getFund());
        entity.setPostFund(acct.getFund());
        entity.setPreFreezeFund(acct.getFreezeFund());
        entity.setPostFreezeFund(acct.getFreezeFund());
        entity.setPreNotFund(acct.getNotFund());
        entity.setPostNotFund(acct.getNotFund());

        entity.setRelObjType(param.getRelObjType());
        entity.setRelObjNo(param.getRelObjNo());
        entity.setMemo(param.getMemo());
        entity.addCreateParam(userInfo);
        return entity;
    }

    private AccountOpLogInfo updateAccountOpLog(AccountOpLogInfo entity, AccountInfo acct) {
        entity.setPostTotalFund(acct.getTotalFund());
        entity.setPostFund(acct.getFund());
        entity.setPostFreezeFund(acct.getFreezeFund());
        entity.setPostNotFund(acct.getNotFund());
        return entity;
    }


    @Override
    public void acctOp(AcctOpRequest param, LoginSimpleUserInfo userInfo) {
        log.debug("+++++ acctOp");
        ValidatorUtils.validate(param);

        param.setFundReal(param.getFundOp().subtract(param.getFundTax()));
        param.setRelDate(LocalDateTimeUtils.format_yyyyMMddHHmmss());

        if (param.getFundReal().doubleValue() <= 0) {
            throw new BusinessException(BaseReturnEnum.PARAM_ERROR);
        }
        AcctTypeEnum acctType = this.getEnum(AcctTypeEnum.class, param.getType(), "不支持的");
        AcctOpTypeEnum opType = this.getEnum(AcctOpTypeEnum.class, param.getOpType(), "不支持的账户操作类型");
        AccountInfo acct = this.getAccountInfo(param.getUserId(), acctType.getCode(), userInfo);
        AccountBillInfo acctBillSettle = this.getAccountBillSettle(param, acct.getCompanyNo(), userInfo);
        AccountOpLogInfo opLogInfo = this.getAccountOpLog(param, acct, opType, userInfo);

        switch (opType) {
            case Add_Acct: //fund(+)
                log.debug("+++++ Add_Acct");
                //账户
                acct.setFund(acct.getFund().add(param.getFundReal()));
                acct.setTotalFund(acct.getTotalFund().add(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                if (BaseConstant.YNEnum.YES.getCode().equals(param.getIsAcctDetail())) {
                    AccountDetailInfo acctDetail = this.getAccountDetailValidity(param.getFundReal(), param.getRelObjType(), param.getRelObjNo(), param.getOverdueFundDate(), acct, userInfo);
                    accountDetailInfoService.save(acctDetail);
                }
                //账单
                acctBillSettle.setInOut(AcctInOutEnum.In_Acct.getCode());
                accountBillInfoService.save(acctBillSettle);
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;
            case Sub_Acct: //fund(-)
                if (param.getFundReal().doubleValue() > acct.getFund().doubleValue()) {
                    throw new BusinessException(AcctErrorEnum.Acct_Insufficient);
                }
                //账户
                acct.setFund(acct.getFund().subtract(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                if (BaseConstant.YNEnum.YES.getCode().equals(param.getIsAcctDetail())) {
                    List<AccountDetailInfo> detailList = this.getAccountDetailConsume(param.getFundReal(), param.getRelObjType(), param.getRelObjNo(), acct, userInfo);
                    if (!CollectionUtils.isEmpty(detailList)) {
                        accountDetailInfoService.updateBatchById(detailList);
                    }
                }
                //账单
                acctBillSettle.setInOut(AcctInOutEnum.Out_Acct.getCode());
                accountBillInfoService.save(acctBillSettle);
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;
            case Sub_Force_Acct:
                //账户
                acct.setFund(acct.getFund().subtract(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                if (BaseConstant.YNEnum.YES.getCode().equals(param.getIsAcctDetail())) {
                    List<AccountDetailInfo> detailList = this.getAccountDetailConsume(param.getFundReal(), param.getRelObjType(), param.getRelObjNo(), acct, userInfo);
                    if (!CollectionUtils.isEmpty(detailList)) {
                        accountDetailInfoService.updateBatchById(detailList);
                    }
                }
                //账单
                acctBillSettle.setInOut(AcctInOutEnum.Out_Acct.getCode());
                accountBillInfoService.save(acctBillSettle);
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;
            case Freeze_Acct: //冻结 fund(-),freezeFund(+)
                if (param.getFundReal().doubleValue() > acct.getFund().doubleValue()) {
                    throw new BusinessException(AcctErrorEnum.Acct_Insufficient);
                }
                //账户
                acct.setFund(acct.getFund().subtract(param.getFundReal()));
                acct.setFreezeFund(acct.getFreezeFund().add(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                //账单
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;
            case Freeze_Sub_Acct: //冻结扣减 freezeFund(-)
                if (param.getFundReal().doubleValue() > acct.getFreezeFund().doubleValue()) {
                    throw new BusinessException(AcctErrorEnum.Acct_Insufficient);
                }
                //账户
                acct.setFreezeFund(acct.getFreezeFund().subtract(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                if (BaseConstant.YNEnum.YES.getCode().equals(param.getIsAcctDetail())) {
                    List<AccountDetailInfo> detailList = this.getAccountDetailConsume(param.getFundReal(), param.getRelObjType(), param.getRelObjNo(), acct, userInfo);
                    if (!CollectionUtils.isEmpty(detailList)) {
                        accountDetailInfoService.updateBatchById(detailList);
                    }
                }
                //账单
                acctBillSettle.setInOut(AcctInOutEnum.Out_Acct.getCode());
                accountBillInfoService.save(acctBillSettle);
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;
            case Freeze_Un_Acct://冻结解冻 fund(+),freezeFund(-)
                if (param.getFundReal().doubleValue() > acct.getFreezeFund().doubleValue()) {
                    throw new BusinessException(AcctErrorEnum.Acct_Insufficient);
                }
                //账户
                acct.setFund(acct.getFund().add(param.getFundReal()));
                acct.setFreezeFund(acct.getFreezeFund().subtract(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                //账单
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;
            case Add_Not_Acct: //未达账新增 notFund(+)
                //账户
                acct.setNotFund(acct.getNotFund().add(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                //账单(结算中)
                acctBillSettle.setInOut(AcctInOutEnum.In_Acct.getCode());
                acctBillSettle.setState(AcctBillStateEnum.Not_Settle.getCode());
                accountBillInfoService.save(acctBillSettle);
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;
            case Sub_Not_Acct: //未达账扣减 notFund(-)
                if (param.getFundReal().doubleValue() > acct.getNotFund().doubleValue()) {
                    throw new BusinessException(AcctErrorEnum.Acct_Insufficient);
                }
                //账户
                acct.setNotFund(acct.getNotFund().subtract(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户

                //账单(结算中)
                List<AccountBillInfo> updateBill = this.getAccountBillSettleIng(param, AcctBillStateEnum.Not_Settle, userInfo);
                accountBillInfoService.updateBatchById(updateBill);

                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;
            case NotAcct_To_Acct: //notFund(-),fund(+)
                if (param.getFundReal().doubleValue() > acct.getNotFund().doubleValue()) {
                    throw new BusinessException(AcctErrorEnum.Acct_Insufficient);
                }
                //账户
                acct.setNotFund(acct.getNotFund().subtract(param.getFundReal()));
                acct.setFund(acct.getFund().add(param.getFundReal()));
                acct.setTotalFund(acct.getTotalFund().add(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                if (BaseConstant.YNEnum.YES.getCode().equals(param.getIsAcctDetail())) {
                    AccountDetailInfo acctDetail = this.getAccountDetailValidity(param.getFundReal(), param.getRelObjType(), param.getRelObjNo(), param.getOverdueFundDate(), acct, userInfo);
                    accountDetailInfoService.save(acctDetail);
                }
                //账单
                List<AccountBillInfo> updateAcctBill = this.getAccountBillSettleIng(param, AcctBillStateEnum.Not_Settle, userInfo);
                accountBillInfoService.updateBatchById(updateAcctBill); //结算中

                acctBillSettle.setInOut(AcctInOutEnum.In_Acct.getCode());
                accountBillInfoService.save(acctBillSettle); //已结算
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;

            case Acct_To_NotAcct: //notFund(+),fund(-)
                if (param.getFundReal().doubleValue() > acct.getFund().doubleValue()) {
                    throw new BusinessException(AcctErrorEnum.Acct_Insufficient);
                }
                //账户
                acct.setFund(acct.getFund().subtract(param.getFundReal()));
                acct.setNotFund(acct.getNotFund().add(param.getFundReal()));
                accountInfoService.updateById(acct);
                //明细账户
                if (BaseConstant.YNEnum.YES.getCode().equals(param.getIsAcctDetail())) {
                    List<AccountDetailInfo> detailList = this.getAccountDetailConsume(param.getFundReal(), param.getRelObjType(), param.getRelObjNo(), acct, userInfo);
                    if (!CollectionUtils.isEmpty(detailList)) {
                        accountDetailInfoService.updateBatchById(detailList);
                    }
                }
                //账单
                List<AccountBillInfo> updateAcctBill2 = this.getAccountBillSettleIng(param, AcctBillStateEnum.Already_Settle, userInfo);
                accountBillInfoService.updateBatchById(updateAcctBill2);  //已结算

                acctBillSettle.setInOut(AcctInOutEnum.In_Acct.getCode());
                acctBillSettle.setState(AcctBillStateEnum.Not_Settle.getCode());
                accountBillInfoService.save(acctBillSettle); //未结算
                //账户操作日志
                this.updateAccountOpLog(opLogInfo, acct);
                accountOpLogInfoService.save(opLogInfo);
                break;

        }
    }

    private AccountInfo getAccountInfo(Integer userId, String type, LoginSimpleUserInfo userInfo) {
        AccountInfo entity = accountInfoService.getOne(new QueryWrapper<AccountInfo>()
                .eq("user_id", userId)
                .eq("type", type)
        );
        if (entity != null) {
            entity.setState(AcctStateEnum.NORMAL.getCode());
            entity.addEditParam(userInfo);
            return entity;
        }
        log.debug("++++++ save accountInfo");
        //todo:取用户信息
        entity = new AccountInfo();
        entity.setCompanyNo("TP");
        entity.setUserId(userId);
        entity.setType(type);
        entity.setTotalFund(BigDecimal.ZERO);
        entity.setFund(BigDecimal.ZERO);
        entity.setFreezeFund(BigDecimal.ZERO);
        entity.setNotFund(BigDecimal.ZERO);
        entity.setState(AcctStateEnum.NORMAL.getCode());
        entity.addCreateParam(userInfo);
        entity.addEditParam(userInfo);
        accountInfoService.save(entity);
        return entity;
    }


}
